/**
 * 将canvas转为 Blob
 * @param {HTMLElement} canvas
 * @param {String} type - 类型
 * @param {Number} quality - 清晰度
 * @return {Promise<Blob>}
 */
export const canvasToBlob = async (canvas, type, quality) => {
  return new Promise(resolve => canvas.toBlob(resolve, type, quality));
};
/**
 * 克隆canvas
 * @param {canvas} oldCanvas - 被克隆的canvas
 * @param {Number} ratio - 缩放比
 * @return {canvas}
 */
export const cloneCanvas = (oldCanvas, ratio = 1) => {
  const newCanvas = document.createElement('canvas');
  const ctx = newCanvas.getContext('2d');
  newCanvas.width = oldCanvas.width * ratio;
  newCanvas.height = oldCanvas.height * ratio;
  ctx.scale(ratio, ratio); // 设置新 Canvas 上下文的缩放比例
  ctx.drawImage(oldCanvas, 0, 0);
  return newCanvas;
};
/**
 * 获取canvas支持下的图片文件格式
 * @param {String|File|Blob} input - 图片路径或文件
 * @return {String}
 */
export const getCanvasImageContentType = input => {
  const mimeDict = {
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    png: 'image/png',
    gif: 'image/gif',
    bmp: 'image/bmp',
    webp: 'image/webp',
  };
  if (typeof input === 'string' && input.trim().length > 0) return mimeDict[input.split('?')[0].split('.').pop().toLocaleLowerCase()] || null;
  else if (input instanceof Blob || input instanceof File) return input.type;
  return null;
};
/**
 * 将图片转为 canvas
 * @param {String|File|Blob|HTMLElement} input - 图片路径或文件
 * @return {canvas}
 */
export const imageToCanvas = input => {
  return new Promise((resolve, reject) => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = function () {
      canvas.width = img.width;
      canvas.height = img.height;
      ctx.drawImage(img, 0, 0);
      if (img.src) resolve(canvas);
    };
    img.onerror = reject;
    if (input && typeof input === 'string') img.src = input;
    else if ((input && input instanceof File) || input instanceof Blob) img.src = URL.createObjectURL(input);
    else if (input && input instanceof HTMLElement && input.tagName.toLowerCase() === 'img') img.src = input.src;
    else if (input && input instanceof HTMLElement && input.tagName.toLowerCase() === 'canvas') resolve(input);
    else resolve(canvas);
  });
};

/**
 * 给图片添加水印
 * @param {String|File|Blob} input - 图片路径或文件
 * @param {String} watermarkText - 水印文字
 * @param {Object} options - 配置项
 * @param {Number|String} options.fontSize - 字体大小 12(默认)
 * @param {String} options.fontFamily - 字体
 * @param {String|Function} options.color - 字体颜色
 * @param {String|Array} options.position - 位置,字符串：t、l、r、b、tr、tl、bl、br，数组时为[x,y]
 * @param {Number} options.angle - 旋转角度
 * @param {Number} options.repeat - 重复间隔
 * @param {Number} options.lineHeight - 行间间隔
 * @param {Number} options.quality - 输出质量 0.8(默认)
 * @param {String} options.contentType - 输出图片格式类型:'image/jpeg'(默认)
 * @param {String} options.resultType - 输出类型:blob base64 canvas(默认)
 * @param {Number} options.size - 输出压缩大小
 * @return {Promise<Blob|Canvas|String>} - 根据参数 options.resultType 适配
 */
export const addWatermark = async (input, watermarkText = '', options) => {
  const contentType = getCanvasImageContentType(input) || 'image/jpeg';
  // 提供默认的压缩参数和返回值类型
  const defaultOptions = {
    quality: 0.8,
    resultType: 'base64',
    contentType,
    fontSize: 12,
    ...options,
  };
  let canvas = await imageToCanvas(input);
  // 先执行一遍压缩，避免图片太大写入水印时间过长
  let detailed = await compressCanvasToBlob(canvas, { contentType, ...options, detailed: true });
  canvas = canvas != detailed.targetCanvas ? cloneCanvas(detailed.targetCanvas) : canvas;
  const fontFamily = defaultOptions.fontFamily || 'Arial';
  const colorFn = typeof defaultOptions.color == 'function' ? defaultOptions.color : (...args) => defaultOptions.color || 'rgba(255, 255, 255, 0.2)';
  ctx = canvas.getContext('2d');
  ctx.font = typeof defaultOptions.fontSize == 'string' ? defaultOptions.fontSize : `${defaultOptions.fontSize}px ${fontFamily}`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  // 重新映射画布上的 (0,0) 位置
  ctx.translate(canvas.width / 2, canvas.height / 2);
  ctx.rotate((defaultOptions.angle * Math.PI) / 180);
  // 边缘补偿，通过检查大写字母M的长度，可以得到与垂直高度非常接近的高度
  let compH = ctx.measureText('M').width;
  // 文字宽度为实际字数的宽度和
  let compW = compH * watermarkText.length;
  if (typeof defaultOptions.position == 'string' && defaultOptions.position.length > 0) {
    let x = 0,
      y = 0;
    defaultOptions.position.split('').forEach(pos => {
      if (pos === 't') y = -canvas.height / 2 + compH;
      else if (pos === 'b') y = canvas.height / 2 - compH;
      else if (pos === 'l') x = -canvas.width / 2 + compW;
      else if (pos === 'r') x = canvas.width / 2 - compW;
    });
    ctx.fillStyle = colorFn();
    ctx.fillText(watermarkText, x, y);
  } else if (Array.isArray(defaultOptions.position) && defaultOptions.position.length > 0) {
    ctx.fillStyle = colorFn();
    ctx.fillText(watermarkText, defaultOptions.position[0], defaultOptions.position[1]);
  } else {
    // 最大边
    // let maxLine = Math.max(canvas.width, canvas.height);
    // 设置每行水印文字的高度为maxLine的1/10
    let lineHeight = defaultOptions.lineHeight || -compH * 3;
    // 设置不同行之间的距离为行高的2倍
    let repeat = defaultOptions.repeat || compW * 1.5;
    if (Math.abs(lineHeight) >= repeat) lineHeight = -repeat / 2;
    // 对角线长度
    const diagonal = Math.ceil(Math.sqrt(canvas.width ** 2 + canvas.height ** 2));
    for (let i = -diagonal; i < diagonal; i += repeat) {
      for (let j = -diagonal; j < diagonal; j += lineHeight + repeat) {
        if (i < -canvas.width || i > canvas.width || j < -canvas.height || j > canvas.height) continue; // 超出图片边界的位置不需要填充
        ctx.fillStyle = colorFn(defaultOptions, i, j);
        ctx.fillText(watermarkText, i, j);
      }
    }
  }
  // 如果进行过压缩则检查添加水印后的体积
  if (detailed) {
    detailed = await compressCanvasToBlob(canvas, { contentType, ...options, detailed: true });
    canvas = canvas != detailed.targetCanvas ? cloneCanvas(detailed.targetCanvas) : canvas;
  }
  if (defaultOptions.resultType === 'blob') return detailed ? detailed.blob : await canvasToBlob(canvas, defaultOptions.contentType, defaultOptions.quality);
  else if (defaultOptions.resultType === 'base64') return canvas.toDataURL(defaultOptions.contentType, detailed ? detailed.quality : defaultOptions.quality);
  else return canvas;
};

/**
 * 压缩canvasToBlob，限制canvas导出的blob大小
 * @param {HTMLCanvasElement} canvas - 用于导出图片的canvas元素
 * @param {Object} options -  选项
 * @param {Number} options.size - 最大文件大小，默认为不限制
 * @param {Number} options.quality - 固定质量 ，不设置的情况下进行自适应
 * @param {Number} options.maxQuality - 输出最大质量，默认为 0.96
 * @param {Number} options.minQuality - 输出最小质量，默认为 0.5
 * @param {Number} options.minWidth - 输出图片的最小宽度，默认为 800
 * @param {Number} options.minHeight - 输出图片的最小高度，默认为 800
 * @param {Number} options.accuracy - 压缩精度，允许在限制上下浮动,精度越低速度越快，范围为0.8-0.99，默认为0.95
 * @param {String} options.contentType - 输出图片格式类型，默认为 'image/jpeg'
 * @param {Boolean} options.detailed - 是否输出详细信息（可用于调试查看压缩信息），默认为 false
 * @return {Promise<Blob|Object>} 返回Blob对象或包含详细信息的对象
 */
export const compressCanvasToBlob = async (canvas, options = {}) => {
  // 提供默认的压缩参数和返回值类型
  const defaultOptions = {
    size: 0,
    quality: 0,
    maxQuality: 0.96,
    minQuality: 0.5,
    minWidth: 100,
    minHeight: 100,
    accuracy: 0.95,
    contentType: 'image/jpeg',
    detailed: false,
    ...options,
  };
  let quality = defaultOptions.quality || defaultOptions.maxQuality;
  let blob = await canvasToBlob(canvas, defaultOptions.contentType, quality);
  // 如果size未设置或值太小，blob大小小于等于设置的最大体积直接返回原始Blob对象
  if (!defaultOptions.size || defaultOptions.size < 1024 || defaultOptions.size >= blob.size) return defaultOptions.detailed ? { ...defaultOptions, canvas, ratio: 1, quality, blob, targetCanvas: canvas } : blob;
  // 默认精度0.95
  if (!defaultOptions.accuracy || defaultOptions.accuracy < 0.8 || defaultOptions.accuracy > 0.99) config.accuracy = 0.95;
  // 返回Blob值大小限制范围
  const resultSize = {
    max: defaultOptions.size * (2 - defaultOptions.accuracy),
    accurate: defaultOptions.size,
    min: defaultOptions.size * defaultOptions.accuracy,
  };
  let high = 1,
    low = 0,
    ratio = 1,
    lastVal = 0,
    targetCanvas = null;
  // 快速缩放到适当的尺寸，确保图片质量的情况下优先缩放图片的宽高
  // 限制最大循环次数为7
  for (let x = 1; x <= 7 && low < high; x++) {
    ratio = Math.round(((low + high) / 2) * 100) / 100;
    if (ratio == lastVal) break;
    lastVal = ratio;
    if (defaultOptions.minHeight > 0 && canvas.height * ratio < defaultOptions.minHeight) break;
    if (defaultOptions.minWidth > 0 && canvas.width * ratio < defaultOptions.minWidth) break;
    targetCanvas = cloneCanvas(canvas, ratio);
    blob = await canvasToBlob(targetCanvas, defaultOptions.contentType, quality);
    // console.log(`[缩放]`, ratio, blob.size, targetCanvas.height, targetCanvas.width);
    if (resultSize.max < blob.size) high = ratio;
    else if (resultSize.min > blob.size) low = ratio;
    else break;
  }
  // 固定质量的情况下跳过画质压缩
  if (!defaultOptions.quality && blob.size > defaultOptions.size) {
    lastVal = 0;
    low = defaultOptions.minQuality;
    high = defaultOptions.maxQuality;
    // 根据图片质量进行压缩，此时的 low，high 代表的是图像质量
    // 限制最大循环次数为7
    for (let x = 1; x <= 7 && low < high; x++) {
      targetCanvas = cloneCanvas(canvas, ratio);
      if (quality > defaultOptions.maxQuality || low > quality) break;
      blob = await canvasToBlob(targetCanvas, defaultOptions.contentType, quality);
      if (blob.size == lastVal) break;
      lastVal = blob.size;
      //   console.log(`[质量]`, quality, blob.size);
      if (resultSize.max < blob.size) quality -= 0.5 ** (x + 1);
      else if (resultSize.min > blob.size) quality += 0.5 ** (x + 1);
      else break;
    }
  }
  // 返回结果
  return defaultOptions.detailed ? { ...defaultOptions, canvas, ratio, quality, blob, targetCanvas } : blob;
};

/**
 * 压缩图片
 * @param {String|File|Blob|HTMLElement} input - 输入图片，支持url，文件，Blob,img节点
 * @param {Object} options -  选项
 * @param {Number} options.size - 最大文件大小，默认为不限制
 * @param {Number} options.quality - 固定质量 ，不设置的情况下进行自适应
 * @param {Number} options.maxQuality - 输出最大质量，默认为 0.96
 * @param {Number} options.minQuality - 输出最小质量，默认为 0.5
 * @param {Number} options.minWidth - 输出图片的最小宽度，默认为 800
 * @param {Number} options.minHeight - 输出图片的最小高度，默认为 800
 * @param {Number} options.accuracy - 压缩精度，允许在限制上下浮动,精度越低速度越快，范围为0.8-0.99，默认为0.95
 * @param {String} options.contentType - 输出图片格式类型，默认为 'image/jpeg'
 * @param {Boolean} options.detailed - 是否输出详细信息（用于输出调试信息），默认为 false
 * @return {Promise<Blob|Object>} 返回Blob对象或包含详细信息的对象
 */
export const compressImage = async (input, options = {}) => {
  return await compressCanvasToBlob(await imageToCanvas(input), options);
};
